package cn.xiaoke.test.y22m03;

import cn.hutool.core.img.ImgUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.Data;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class 合同模板_案例 {
    @Data
    public static class Student {
        String name;
        String gender;
        Integer age;
        String card;

        public Student(String name, String gender, Integer age, String card) {
            this.name = name;
            this.gender = gender;
            this.age = age;
            this.card = card;
        }
    }

    static SAXReader saxReader = new SAXReader();

    static StandardEvaluationContext context;

    static {
        context = new StandardEvaluationContext();
        List<PropertyAccessor> propertyAccessors = Collections.singletonList(new MapAccessor());
        context.setPropertyAccessors(propertyAccessors);
    }

    public static void main(String[] args) {

        final Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("signature", "http://image-linshi.xiaokedamowang.cn/%E6%B0%B4%E5%8D%B0.png");
        final List<Student> stuList = new ArrayList<>();
        stuList.add(new Student("张三", "男", 11, "123"));
        stuList.add(new Student("李四", "女", 21, "223"));
        stuList.add(new Student("王五", "男", 32, "321"));
        dataMap.put("stuList",stuList);

        final InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("test_1.xml");
        final Document document = loadDocument(resourceAsStream);

        final List<Element> tblList = findElementByName(document.getRootElement(), "tbl");
        tblList.forEach(tbl ->{
            final HasAnnotationTagResult result = recursionHasAnnotationTagResult(tbl);
            if (!result.has) {
                return;
            }
            final Map<String, String> map = textToMap(result.value);
            StringBuilder xmlStrBuilder = new StringBuilder("#list");
            xmlStrBuilder.append(" ").append(map.get("source")).append(" ").append("as").append(" ").append(map.get("var")).append(" ");
//            <#list stuList as user >
//            </#list>
            final QName aQname = DocumentHelper.createQName("abc");
            try {
                final Field name = QName.class.getDeclaredField("name");
                final Field qualifiedName = QName.class.getDeclaredField("qualifiedName");
                name.setAccessible(true);
                qualifiedName.setAccessible(true);
                name.set(aQname, xmlStrBuilder.toString());
                qualifiedName.set(aQname, "#list");
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
            final Element newEle = DocumentHelper.createElement(aQname);
            System.out.println(newEle.getName());
            final Element tr = tbl.elements("tr").get(1);
            tbl.remove(tr);
            newEle.add(tr);
            tbl.add(newEle);
        });

        //处理图片
        final List<Element> pList = findElementByName(document.getRootElement(), "p");
        final List<Element> followUpPList = pList.stream().filter(getChildHaveCondition("annotation"))
                .collect(Collectors.toList());

        followUpPList.forEach(element -> {

            final List<Element> children = element.elements();

            final Predicate<Element> rCondition = getNameCondition("r");
            final Predicate<Element> anCondition = getNameCondition("annotation");

            children.stream().filter(anCondition).forEach(child -> child.getParent().remove(child));

            final Predicate<Element> childOtherCondition = rCondition.and(getChildOtherCondition(anCondition
                    .and(getAttributeTypeCondition("Word.Comment"))));
            final List<Element> list = children.stream().filter(childOtherCondition).collect(Collectors.toList());
            list.forEach(ele ->{
                final String text = ele.element("annotation").element("content").element("p").element("r").element("t").getText();
                final Map<String, String> map = textToMap(text);

                final List<Element> rList = ele.getParent().elements("r");
                Element binData = null;
                for (int i = 0; i < rList.size(); i++) {
                    if (rList.get(i) == ele){
                        binData = rList.get(i - 1).element("pict").element("binData");
                        break;
                    }
                }

                if (map.get("type").equals("url")) {
                    final BufferedImage bufferedImage;
                    try {
                        String path = expression(dataMap, map.get("path"));
                        bufferedImage = ImgUtil.read(new URL(path));
                        final String base64 = ImgUtil.toBase64(bufferedImage, path.substring(path.lastIndexOf(".") + 1));
                        if (binData != null){
                            binData.setText(base64);
                        }
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                    }
                }
                ele.getParent().remove(ele);
            });
        });

        try (
                FileWriter fileWriter = new FileWriter("test_2.ftl");
                FileWriter fileWriter2 = new FileWriter("test_2.docx");
                MyXMLWriter writer = new MyXMLWriter(fileWriter);

        ) {
//            XMLWriter writer = new XMLWriter(fileWriter);
            writer.write(document);
            writer.flush();
            Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
            configuration.setDefaultEncoding("utf-8");
            configuration.setDirectoryForTemplateLoading(new File("E:\\java_project\\bilibili"));
            final Template template = configuration.getTemplate("test_2.ftl");
            template.process(dataMap, fileWriter2);
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }

    public static class MyXMLWriter extends XMLWriter implements AutoCloseable{

        public MyXMLWriter(Writer writer) {
            super(writer);
        }

        @Override
        protected void writeElement(Element element) throws IOException {
            int size = element.nodeCount();
            final String name = element.getName();
            String qualifiedName = element.getQualifiedName();
            if (name != null && name.startsWith("#list")) {
                writer.write("<");
                writer.write(name);
                if (size <= 0) {
                    writeEmptyElementClose(qualifiedName);
                } else {
                    writer.write(">");
                    writeElementContent(element);
                    writer.write("</");
                    writer.write(qualifiedName);
                    writer.write(">");
                }
            } else {
                super.writeElement(element);
            }
        }
    }

    private static String expression(Map<String, Object> dataMap, String expressionStr) {
        ExpressionParser parser = new SpelExpressionParser();
        SpelExpression expression = (SpelExpression) parser.parseExpression(expressionStr, new TemplateParserContext());
        expression.setEvaluationContext(context);
        return expression.getValue(dataMap, String.class);
    }

    private static Map<String, String> textToMap(String text) {
        final Map<String, String> map = new HashMap<>();
        final String[] kvs = text.split(",");
        for (String kvStr : kvs) {
            final String[] kv = kvStr.split("=");
            map.put(kv[0].trim(), kv[1].trim());
        }
        return map;
    }

    public static Document loadDocument(InputStream inputStream) {
        Document read = null;
        try {
            read = saxReader.read(inputStream);
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return read;
    }

    private static Predicate<Element> getNameCondition(String name) {
        return elem -> name.equals(elem.getName());
    }

    private static Predicate<Element> getAttributeTypeCondition(String attributeName) {
        return elem -> attributeName.equals(elem.attribute("type").getValue());
    }

    private static Predicate<Element> getChildHaveCondition(String name) {
        final Predicate<Element> childNameCondition = getNameCondition(name);
        return elem -> elem.elements().stream().anyMatch(childNameCondition);
    }

    private static Predicate<Element> getChildOtherCondition(Predicate<Element> predicate) {
        return elem -> elem.elements().stream().anyMatch(predicate);
    }

    public static List<Element> findElementByName(Element root, String name) {
        final Predicate<Element> nameCondition = getNameCondition(name);
        return bfsFindElementsByCondition(root, nameCondition);
    }

    private static List<Element> bfsFindElementsByCondition( Element root,Predicate<Element> condition) {
        final List<Element> ret = new ArrayList<>();
        if (root == null) {
            return ret;
        }
        final Queue<Element> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()){
            final Element poll = queue.poll();
            if (condition.test(poll)) {
                ret.add(poll);
            }else{
                queue.addAll(poll.elements());
            }
        }
        return ret;
    }

    private static HasAnnotationTagResult recursionHasAnnotationTagResult(
            Element root) {
        final HasAnnotationTagResult result = HasAnnotationTagResult.elementToHasAnnotationTagResult(root);
        return root.elements().stream().map(合同模板_案例::recursionHasAnnotationTagResult).reduce(result, HasAnnotationTagResult::merge);
    }

    static class HasAnnotationTagResult {
        boolean has;
        String value;

        public HasAnnotationTagResult(boolean has) {
            this.has = has;
        }

        public HasAnnotationTagResult(boolean has, String value) {
            this.has = has;
            this.value = value;
        }

        @Override
        public String toString() {
            return "HasAnnotationTagResult{" +
                    "has=" + has +
                    ", value='" + value + '\'' +
                    '}';
        }
        
        public HasAnnotationTagResult merge(HasAnnotationTagResult other){
            this.has |= other.has;
            this.value = other.value == null ? this.value : other.value;
            return this;
        }
        
        public static HasAnnotationTagResult elementToHasAnnotationTagResult(Element element){
            final HasAnnotationTagResult result = new HasAnnotationTagResult(false);

            if (element != null && getNameCondition("annotation").test(element)) {
                final String value = element.attribute("type").getValue();
                final Element parent = element.getParent();
                parent.remove(element);
                result.has = true;
                if ("Word.Comment".equals(value)) {
                    final Element valAnnoTag = element.element("content").element("p").element("r").element("t");
                    result.value = valAnnoTag.getText();
                    parent.getParent().remove(parent);
                }
            }
            return result;
        }

    }

}
